/*
 *  Copyright 2011.
 * 
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 * 
 *       http://www.apache.org/licenses/LICENSE-2.0
 * 
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *  under the License.
 */
package org.opu.pc.ui.util;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Properties;

/**
 * It's very simple application and we don't need Apache logger - it to MONSTR.
 * So we will use this logger. For creating new instance use :
 * {@link #create(java.lang.Class) }. If you want to log simple message you can
 * get simple instance: {@link #getInstance()}.
 * <pre>
 *  Config file can be:
 *  out = <b>stdout</b> | stderr | file:{.+}
 *  level = [0, 1, 2, 3, 4] | DEBUG | INFO | WARNING | <b>ERROR</b> | FATAL
 *  data-format = yyyy.MM.dd 'at' HH:mm:ss | now ...
 * </pre>
 * <ol start=0>
 *  <b>Levels: </b>
 *  <li> LVL_DEBUG </li>
 *  <li> LVL_INFO </li>
 *  <li> LVL_WARNING </li>
 *  <li> LVL_ERROR </li>
 *  <li> LVL_FATAL </li>
 * </ol>
 */
public final class Logger {

    public static final String PROP_OUT = "out";
    public static final String PROP_LVL = "level";
    public static final String PROP_DATAFORMAT = "data-format";
    public static final String OUT_STDOUT = "stdout";
    public static final String OUT_STDERR = "stderr";
    public static final String OUT_FILE_PREFIX = "file:";
    public static final String OUT_DEFAULT_OUT = OUT_STDOUT;

    public static final int LVL_DEBUG = 0;
    public static final int LVL_INFO = 1;
    public static final int LVL_WARNING = 2;
    public static final int LVL_ERROR = 3;
    public static final int LVL_FATAL = 4;
    public static final int LVL_DEFAULT = LVL_ERROR;

    public static final String DEBUG = "   DEBUG";
    public static final String INFO = "    INFO";
    public static final String WARNING = " WARNING";
    public static final String ERROR = "   ERROR";
    public static final String FATAL = "   FATAL";
    public static final String DATAFORMAT_EMPTY = "now";
    public static final String DATAFORMAT_DEFAULT = "[yyyy.MM.dd 'at' HH:mm:ss]";

    private static Logger instance = new Logger();

    private int level;
    private PrintStream output;
    private SimpleDateFormat dateFormat;
    private Properties prop;

    private Class<?> defaultClazz;

    static PrintStream getOut(String str) throws IOException {
        str = str.trim();
        if (str.equalsIgnoreCase(OUT_STDOUT)) {
            return System.out;
        } else if (str.equalsIgnoreCase(OUT_STDERR)) {
            return System.err;
        } else if (str.toLowerCase().startsWith(OUT_FILE_PREFIX)) {
            str = str.substring(5);
            File f = new File(str);
            f.getParentFile().mkdirs();
            return new PrintStream(new FileOutputStream(str));
        } else {
            return getOut(OUT_DEFAULT_OUT);
        }
    }

    private Logger() {
        File logPropFile = ConfigPath.getInstance().getLogProp();
        String rex = null;
        String rex2 = null;
        String rex3 = null;

        if (logPropFile != null && logPropFile.exists()) {
            try {
                prop = FileIO.load(logPropFile, prop);
            } catch (IOException ex) {
                prop = new Properties();
                rex = String.valueOf(ex);
            }
        } else {
            prop = new Properties();
            try {
                FileIO.store(logPropFile, prop,
                        "out = stdout | stderr | file:{.+}\n"
                        + "level = [0, 1, 2, 3, 4] | DEBUG | INFO | WARNING | ERROR | FATAL\n"
                        + "data-format = yyyy.MM.dd 'at' HH:mm:ss | now ...");
                System.out.println(logPropFile);
                System.out.flush();
            } catch (IOException ex) {
            }
        }

        String out = prop.getProperty(PROP_OUT);
        if (out != null && !out.isEmpty()) {
            try {
                output = getOut(out);
            } catch (IOException ex) {
                rex2 = String.valueOf(ex);
                try {
                    output = getOut(OUT_DEFAULT_OUT);
                } catch (IOException ex1) {
                    output = System.err;
                    rex2 += "\n\t" + ex1;
                }
            }
        } else {
            try {
                output = getOut(OUT_DEFAULT_OUT);
            } catch (IOException ex1) {
                rex2 = String.valueOf(ex1);
                output = System.err;
            }
        }

        String lvl = prop.getProperty(PROP_LVL);
        if (lvl != null && !lvl.isEmpty()) {
            try {
                level = getLogLvl(lvl);
            } catch (NumberFormatException ex) {
                level = LVL_DEFAULT;
                rex3 = String.valueOf(ex);
            }
        } else {
            level = LVL_DEFAULT;
        }

        String df = prop.getProperty(PROP_DATAFORMAT);
        if (df != null && !df.isEmpty()) {
            if (!df.equalsIgnoreCase(DATAFORMAT_EMPTY)) {
                dateFormat = new SimpleDateFormat(df);
            }
        } else {
            dateFormat = new SimpleDateFormat(DATAFORMAT_DEFAULT);
        }

        if (rex != null && !rex.isEmpty()) {
            error(Logger.class, rex);
        }

        if (rex2 != null && !rex2.isEmpty()) {
            error(Logger.class, rex2);
        }

        if (rex2 != null && !rex2.isEmpty()) {
            error(Logger.class, rex3);
        }

        info(Logger.class, "out " + output);
        info(Logger.class, "level " + level);
        info(Logger.class, "data-format " + dateFormat.toPattern());

    }

    public Logger(int level, PrintStream output, SimpleDateFormat dateFormat) {
        this.level = level;
        this.output = output;
        this.dateFormat = dateFormat;
    }

    public void flush() {
        output.flush();
    }

    private int getLogLvl(String lvl) throws NumberFormatException {
        int val = LVL_DEFAULT;

        try {
            val = Integer.parseInt(lvl);
        } catch (NumberFormatException numberFormatException) {
            if (DEBUG.trim().equalsIgnoreCase(lvl)) {
                return LVL_DEBUG;
            } else if (INFO.trim().equalsIgnoreCase(lvl)) {
                return LVL_INFO;
            } else if (WARNING.trim().equalsIgnoreCase(lvl)) {
                return LVL_WARNING;
            } else if (ERROR.trim().equalsIgnoreCase(lvl)) {
                return LVL_ERROR;
            } else if (FATAL.trim().equalsIgnoreCase(lvl)) {
                return LVL_FATAL;
            } else {
                throw numberFormatException;
            }
        }

        return val;
    }

    public void debug(Object obj, Object msg) {
        log(obj, LVL_DEBUG, msg);
    }

    public void debug(Class<?> clazz, Object msg) {
        log(clazz, LVL_DEBUG, msg);
    }

    public void debug(Object msg) {
        debug(null, msg);
    }

    public void info(Object obj, Object msg) {
        log(obj, LVL_INFO, msg);
    }

    public void info(Class<?> clazz, Object msg) {
        log(clazz, LVL_INFO, msg);
    }

    public void info(Object msg) {
        info(null, msg);
    }

    public void warning(Object obj, Object msg) {
        log(obj, LVL_WARNING, msg);
    }

    public void warning(Class<?> clazz, Object msg) {
        log(clazz, LVL_WARNING, msg);
    }

    public void warning(Object msg) {
        warning(null, msg);
    }

    public void error(Object obj, Object msg) {
        log(obj, LVL_ERROR, msg);
    }

    public void error(Class<?> clazz, Object msg) {
        log(clazz, LVL_ERROR, msg);
    }

    public void error(Object msg) {
        error(null, msg);
    }

    public void fatal(Object obj, Object msg) {
        log(obj, LVL_FATAL, msg);
    }

    public void fatal(Class<?> clazz, Object msg) {
        log(clazz, LVL_FATAL, msg);
    }

    public void fatal(Object msg) {
        fatal(null, msg);
    }

    public void log(Object clazz, int lvl, Object msg) {
        if (clazz != null) {
            log(clazz.getClass(), lvl, msg);
        } else {
            log(null, lvl, msg);
        }
    }

    public void log(Class<?> clazz, int lvl, Object msg) {
        if (lvl < level) {
            return;
        }

        StringBuilder sb = new StringBuilder();

        if (dateFormat != null) {
            sb.append(dateFormat.format(new Date()));
        }

        sb.append('[');
        sb.append(lvlToString(lvl));

        sb.append("]: ");

        if (clazz != null || (clazz = defaultClazz) != null) {
            sb.append('[').append(clazz.getSimpleName()).append("] ");
        }

        if (msg instanceof Exception) {
            if (msg != null){
                final String prefix = sb.toString();
                ByteArrayOutputStream bout = new ByteArrayOutputStream();
                ((Exception)msg).printStackTrace(new PrintStream(bout){

                    @Override
                    public void print(String s) {
                        super.print(prefix);
                        super.print(s);
                    }

                });
                sb.delete(0, sb.length());
                sb.append(new String(bout.toByteArray()));
            }
        } else {
            sb.append(msg);
        }

        output.println(sb.toString());

        if (lvl > LVL_WARNING) {
            flush();
        }
    }

    public static Logger getInstance() {
        return instance;
    }

    public static Logger create(Class<?> clazz) {
        Logger clone = instance.clone();
        clone.defaultClazz = clazz;
        return clone;
    }

    @Override
    protected Logger clone()  {
        return new Logger(level, output, dateFormat);
    }

    private String lvlToString(int lvl) {
        switch (lvl) {
            case LVL_DEBUG:
                return DEBUG;
            case LVL_INFO:
                return INFO;
            case LVL_WARNING:
                return WARNING;
            case LVL_ERROR:
                return ERROR;
            case LVL_FATAL:
                return FATAL;
            default:
                return "" + lvl;
        }
    }
}
